package eiisan.forkjointest;

import eiisan.forkjointest.countedcompleter.*;
import eiisan.forkjointest.recursiveaction.Applyer;
import eiisan.forkjointest.recursiveaction.IncrementTask;
import eiisan.forkjointest.recursiveaction.SortTask;
import eiisan.forkjointest.recursivetask.Fibonacci;
import eiisan.forkjointest.recursivetask.FileSizeTask;
import org.junit.Test;

import java.io.File;
import java.util.*;
import java.util.concurrent.CountedCompleter;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class ForkJoinTest {

    ForkJoinPool forkJoinPool = new ForkJoinPool(8);


    @Test
    public void testFiles() {
        System.out.println("Main thread start ==> " + Thread.currentThread().getName());
        String pathName = "G:\\note\\geektime-electron";
        File file = new File(pathName);
        System.out.println("pathName: " + pathName);
        File[] files = {file};
        // iterator:
        long startTime0 = System.currentTimeMillis();
        long fileSize = getFileSize(files);
        long endTime0 = System.currentTimeMillis();
        System.out.println("iterator fileSize:" + fileSize + " B in " + (endTime0 - startTime0) + " ms.");

        // fork/join:
        FileSizeTask task = new FileSizeTask(files);
        long startTime = System.currentTimeMillis();
        long size = forkJoinPool.invoke(task);
        long endTime = System.currentTimeMillis();
        System.out.println("Fork/join fileSize: " + size + " B in " + (endTime - startTime) + " ms.");
    }

    static long getFileSize(File[] files) {
        long sum = 0;
        for (File file : files) {
            if (file.isDirectory()) {
                sum += getFileSize(file.listFiles());
            } else {
                sum += file.length();
            }
        }
        return sum;
    }

    @Test
    public void testTreeSearch() throws ExecutionException, InterruptedException {
        System.out.println("Main thread start ==> " + Thread.currentThread().getName());
        int size = 10000000;
        List<TreeNode> tree = getTree(size);
        System.out.println("size: " + size);
        int traget = (size / 2) + 3;
        System.out.println("search traget ====>> " + traget);
        // iterator:
        TreeNode tragetTreeNode0 = null;
        long startTime0 = System.currentTimeMillis();
        Deque<TreeNode> deque = new ArrayDeque<>(tree);
        while (!deque.isEmpty()) {
            TreeNode curr = deque.removeFirst();
            if (curr.getId() == traget) {
                tragetTreeNode0 = curr;
                break;
            } else {
                deque.addAll(curr.getChildNodes());
            }
        }
        long endTime0 = System.currentTimeMillis();
        System.out.println("search " + (endTime0 - startTime0) + " ms.");

        // fork/join:
        TreeNode tragetTreeNode = null;
        long startTime = System.currentTimeMillis();
        Predicate<TreeNode> predicate = (t) -> {
            return t.getId() == traget;
        };
        SearcherTree.search(tree, predicate);
        long endTime = System.currentTimeMillis();
        System.out.println("Fork/join in " + (endTime - startTime) + " ms.");

    }

    public static List<TreeNode> getTree(int size) {
        List<TreeNode> trees = generateTreeNodes(size);
        Map<Integer, TreeNode> idTreeNodeMap = trees.stream().parallel()
                .collect(Collectors.toMap(TreeNode::getId, Function.identity()));
        List<TreeNode> rootTrees = new ArrayList<>();
        for (TreeNode tree : trees) {
            int pid = tree.getPid();
            if (pid == -1) {
                rootTrees.add(tree);
            } else {
                idTreeNodeMap.get(pid).getChildNodes().add(tree);
            }
        }
        return rootTrees;
    }

    public static List<TreeNode> generateTreeNodes(int size) {
        Random randomChildSize = new Random(11);
        List<TreeNode> treeNodes = new ArrayList<>(size);
        int remaning = size;
        do {
            int i1 = randomChildSize.nextInt(6);
            int min = Math.min(i1, remaning);
            int stepStart = size - remaning;
            for (int i = 0; i < min; i++) {
                TreeNode treeNode = new TreeNode();
                int id = i + stepStart;
                int pid = stepStart > 0 ? randomChildSize.nextInt(stepStart) : -1;
                treeNode.setId(id);
                treeNode.setPid(pid);
                treeNodes.add(treeNode);
            }
            remaning = remaning - min;
        } while (remaning > 0);
        return treeNodes;
    }


    @Test
    public void test2Search() {
        System.out.println("Main thread start ==> " + Thread.currentThread().getName());
        int size = 1000000;
        String[] array = new String[size];
        IntStream
                .range(0, size)
                .parallel()
                .forEach((i) -> array[i] = UUID.randomUUID().toString().substring(0, 10));
        System.out.println("size: " + size);

        String traget = array[(size / 2) + 56];
        System.out.println("search ====>> " + traget);
        // iterator:
        long startTime0 = System.currentTimeMillis();
        String search0 = null;
        int index0 = -1;
        for (int i = 0; i < array.length; i++) {
            String str = array[i];
            if (str.equals(traget)) {
                search0 = str;
                index0 = i;
                break;
            }
        }
        System.out.println(search0);
        long endTime0 = System.currentTimeMillis();
        System.out.println("search idx " + index0 + "in " + (endTime0 - startTime0) + " ms.");

        // fork/join:
        long startTime = System.currentTimeMillis();
        Predicate<String> predicate = (t) -> {
            return t.equals(traget);
        };
        String search = Searcher.search(array, predicate);
        System.out.println(search);
        long endTime = System.currentTimeMillis();
        System.out.println("Fork/join in " + (endTime - startTime) + " ms.");
    }

    @Test
    public void testSearch() {
        System.out.println("Main thread start ==> " + Thread.currentThread().getName());
        int size = 20000000;
        Integer[] array = new Integer[size];
        for (int i = 0; i < array.length; i++) {
            array[i] = i;
        }
        System.out.println("size: " + size);

        // 简单的查找，直接使用iterator更快速
        // iterator:
        long startTime0 = System.currentTimeMillis();
        Integer search0 = null;
        for (Integer integer : array) {
            if (integer == 16000000) {
                search0 = integer;
                break;
            }
        }
        System.out.println(search0);
        long endTime0 = System.currentTimeMillis();
        System.out.println("search in " + (endTime0 - startTime0) + " ms.");

        // fork/join:
        long startTime = System.currentTimeMillis();
        Predicate<Integer> predicate = (t) -> {
            return t == 16000000;
        };
        Integer search = Searcher.search(array, predicate);
        System.out.println(search);
        long endTime = System.currentTimeMillis();
        System.out.println("Fork/join in " + (endTime - startTime) + " ms.");

    }


    @Test
    public void testStrean() throws ExecutionException, InterruptedException {
        System.out.println("Main thread start ==> " + Thread.currentThread().getName());
        int size = 2000000;
        int[] array = new int[size];
        for (int i = 0; i < array.length; i++) {
            array[i] = i;
        }
        System.out.println("size: " + size);
        //1.stream并行流，使用自定义forkJoinPool
        forkJoinPool.submit(() -> {
            Arrays.stream(array).parallel().forEach((e) -> {
                System.out.println(Thread.currentThread().getName() + " forEach ===> " + e);
            });
        }).get();
        //1.stream并行流，若没有使用forkJoinPool包裹，将使用全局共享commonpool，
        // 需要注意资源占用问题，一处stream并行流堵塞，可能导致所有使用stream并行流的线程堵塞
        Arrays.stream(array).parallel().forEach((e) -> {
            System.out.println(Thread.currentThread().getName() + " parallel forEach ===> " + e);
        });
    }


    @Test
    public void testMapReduce() {
        Integer[] array = {1, 2, 3};
        //方法一.
        Integer result = new MapReducer<Integer>(null, array, (a) -> a + 2, Integer::sum, 0, array.length).invoke();

        System.out.println("方法一result: " + result);
        //方法二我就不抄了,就在官方注释上.
        result = new MapReducer2<Integer>(null, array, (a) -> a + 1
                , Integer::sum, 0, array.length, null).invoke();
        System.out.println("方法二result: " + result);
    }

    @Test
    public void test5() {
        System.out.println("Main thread start ==> " + Thread.currentThread().getName());
        int size = 200;
        String[] array = new String[size];
        for (int i = 0; i < array.length; i++) {
            array[i] = UUID.randomUUID().toString().substring(0, 5);
        }
        System.out.println("size: " + size);
        // fork/join:
        long startTime = System.currentTimeMillis();
        ForEach.forEach(array);
        long endTime = System.currentTimeMillis();
        System.out.println("Fork/join in " + (endTime - startTime) + " ms.");
    }

    @Test
    public void test4() {
        System.out.println("Main thread start ==> " + Thread.currentThread().getName());
        int size = 40;

        System.out.println("size: " + size);
        // fork/join:
        Fibonacci task = new Fibonacci(size);
        long startTime = System.currentTimeMillis();
        Integer invoke = forkJoinPool.invoke(task);
        long endTime = System.currentTimeMillis();
        System.out.println("Fork/join " + invoke + " in " + (endTime - startTime) + " ms.");
    }


    @Test
    public void test0() {
        System.out.println("Main thread start ==> " + Thread.currentThread().getName());
        int size = 2000;
        double[] array = new double[size];
        for (int i = 0; i < array.length; i++) {
            array[i] = randomDouble();
        }
        System.out.println("size: " + size);
        System.out.println("array " + Arrays.toString(array));

        // fork/join:
        Applyer task = new Applyer(array);
        long startTime = System.currentTimeMillis();
        forkJoinPool.invoke(task);
        long endTime = System.currentTimeMillis();
        System.out.println("Fork/join " + Arrays.toString(array) + " in " + (endTime - startTime) + " ms.");
        System.out.println("Fork/join " + task.result + " in " + (endTime - startTime) + " ms.");

    }

    @Test
    public void test1() {
        System.out.println("Main thread start ==> " + Thread.currentThread().getName());
        int size = 2000;
        long[] array = new long[size];
        for (int i = 0; i < array.length; i++) {
            array[i] = random();
        }
        System.out.println("size: " + size);
        System.out.println("array " + Arrays.toString(array));

        // fork/join:
        SortTask task = new SortTask(array);
        long startTime = System.currentTimeMillis();
        forkJoinPool.invoke(task);
        long endTime = System.currentTimeMillis();
        System.out.println("Fork/join " + Arrays.toString(array) + " in " + (endTime - startTime) + " ms.");
    }

    @Test
    public void test2() {
        System.out.println("Main thread start ==> " + Thread.currentThread().getName());
        int size = 2000;
        long[] array = new long[size];
        for (int i = 0; i < array.length; i++) {
            array[i] = i;
        }
        System.out.println("size: " + size);
        System.out.println("array " + Arrays.toString(array));

        // fork/join:
        IncrementTask task = new IncrementTask(array);
        long startTime = System.currentTimeMillis();
        forkJoinPool.invoke(task);
        long endTime = System.currentTimeMillis();
        System.out.println("Fork/join " + Arrays.toString(array) + " in " + (endTime - startTime) + " ms.");
    }

    static Random random = new Random(0);

    static long random() {
        return random.nextInt(10000);
    }

    static double randomDouble() {
        return random.nextDouble();
    }

}
